package com.chinasofti.framework.parser;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Date;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.chinasofti.framework.base.Utils;
import com.chinasofti.framework.data.Entity;

public abstract class XmlParser<T extends Entity>{
	
	protected abstract T createEntityInstance();

	protected static ThreadLocal<DocumentBuilder> DocumentBuilders = 
		new ThreadLocal<DocumentBuilder>() {
		@Override
		protected DocumentBuilder initialValue() {
			try {
				return DocumentBuilderFactory.newInstance()
						.newDocumentBuilder();
			} catch (ParserConfigurationException ex) {
				throw new ExceptionInInitializerError(ex);
			}
		}
	};
	
	public void DoParse(String xmlString, ArrayList<T> entities) throws Exception {
		Document document = DocumentBuilders.get().parse(
				new ByteArrayInputStream(xmlString.getBytes("UTF-8")));	
		DoParse(document, entities);
	}
	
	
	public void DoParse(InputStream inputStream, ArrayList<T> entities) throws Exception {
		Document document = DocumentBuilders.get().parse(inputStream);
		DoParse(document, entities);
	}
	
	public  void DoParse(Document document, ArrayList<T> entities) throws Exception {
		Element root = document.getDocumentElement();
		if(root != null) {
			NodeList nodes = root.getChildNodes();
			int nodesLen = nodes.getLength();
			for(int i = 0; i < nodesLen ; i++) {
				Node node = nodes.item(i);
				if(node.getNodeType() != Node.ELEMENT_NODE) {
					continue;
				}
				
				T entity = createEntityInstance();
				DoParse(node, entity);
				if(entity != null) {
					entities.add(entity);
				}
			}
		}
	}
	
	public void DoParse(String xmlString, T entity) throws Exception {
		Document document = DocumentBuilders.get().parse(
				new ByteArrayInputStream(xmlString.getBytes("UTF-8")));	
		DoParse(document, entity);
	}
	
	public void DoParse(InputStream inputStream, T entity) throws Exception {
		Document document = DocumentBuilders.get().parse(inputStream);
		DoParse(document, entity);
	}
	
	public void DoParse(Document document, T entity) throws Exception {
		Element root = document.getDocumentElement();
		if(root != null) {
			DoParse(root, entity);
		}
	}

	protected void DoParse(Node node, Entity entity) throws Exception{
		NamedNodeMap attrs = node.getAttributes();
		if(attrs != null) {
			parseAtrributes(entity, attrs);
		}
		NodeList childNodes = node.getChildNodes();
		if(childNodes != null) {
			parseChildNodes(entity, childNodes);
		}	
	}

	protected void parseChildNodes(Entity parent, NodeList childNodes) throws Exception {
		for(int i = 0; i < childNodes.getLength(); i++) {
			Node childNode = childNodes.item(i);
			if(childNode.getNodeType() != Node.ELEMENT_NODE) {
				continue;
			}
			
			String nodeName = childNode.getNodeName();
			if(parent.hasField(nodeName)) {
				Type nodeType = getFieldType(parent, nodeName);
				boolean singleData = parent.isFieldSingle(nodeName);
				if(!singleData) {
					if(Utils.isDerived(Entity.class,(Class<?>)nodeType)) {
						ArrayList<Entity> childEntities = parent.getChildEntities(nodeName);
						Class<?> typeClass = (Class<?>)nodeType;
						Entity childEntity = (Entity)typeClass.newInstance();
						XmlParser<? extends Entity> parser = childEntity.getParser();
						parser.DoParse(childNode, childEntity);
						childEntities.add(childEntity);
					}
					else {
						ArrayList<Object> children = new ArrayList<Object>();
						String contentText = getNodeText(childNode);
						Object childValue = getValueFromText(parent, nodeName, nodeType, contentText);
						if(childValue == null) {
							throw new Exception("Parser error!"
									+ " entity type='" + parent.getClass().getName() + "'" 
									+ " field type='" + nodeType.toString() +"'" 
									+ " node name='" + nodeName + "'" 
									+ " value='" + contentText + "'");
						}
						else if(!children.add(childValue)) {
							throw new Exception("Parser error!"
									+ " entity type='" + parent.getClass().getName() + "'" 
									+ " field type='" + nodeType.toString() +"'" 
									+ " node name='" + nodeName + "'" 
									+ " value='" + contentText + "'");
						}
					}
				}
				else {
					if(Utils.isDerived(Entity.class,(Class<?>)nodeType)) {
						Class<?> typeClass = (Class<?>)nodeType;
						Entity childEntity = (Entity)typeClass.newInstance();
						XmlParser<? extends Entity> parser = childEntity.getParser();
						parser.DoParse(childNode, childEntity); 
						parent.set(nodeName, childEntity);
					}	
					else {
						String contentText = getNodeText(childNode);
						Object value = getValueFromText(parent, nodeName, nodeType, contentText);
						if(value == null) {
							throw new Exception("Parser error!"
									+ " entity type='" + parent.getClass().getName() + "'" 
									+ " field type='" + nodeType.toString() +"'" 
									+ " node name='" + nodeName + "'" 
									+ " value='" + contentText + "'");
						}
						else if(!parent.set(nodeName, value)) {
							throw new Exception("Parser error!"
									+ " entity type='" + parent.getClass().getName() + "'" 
									+ " field type='" + nodeType.toString() +"'" 
									+ " node name='" + nodeName + "'" 
									+ " value='" + contentText + "'");
						}
					}
				}
			}
		}
	}
	
	protected Type getFieldType(Entity entity, String nodeName) {
		return entity.getFieldType(nodeName);
	}
	
	protected void parseAtrributes(Entity entity, NamedNodeMap attrs) throws Exception {
		for(int i = 0; i < attrs.getLength(); i++) {
			String nodeName = attrs.item(i).getNodeName();
			if(!entity.hasField(nodeName)) {
				continue;
			}
			String nodeValue = attrs.item(i).getNodeValue();
			Type attributeType = getFieldType(entity, nodeName);

			Object value = this.getValueFromText(entity, nodeName, attributeType, nodeValue);
			
			if(value == null) {
				throw new Exception("Parser error!"
						+ " entity type='" + entity.getClass().getName() + "'" 
						+ " field type='" + attributeType.toString() +"'" 
						+ " node name='" + nodeName + "'" 
						+ " value='" + nodeValue + "'");
			}
			else if(!entity.set(nodeName, value)) {
				throw new Exception("Parser error!"
						+ " entity type='" + entity.getClass().getName() + "'" 
						+ " field type='" + attributeType.toString() +"'" 
						+ " node name='" + nodeName + "'" 
						+ " value='" + nodeValue + "'");
			}
			
		}
	}
	
	protected String getNodeText(Node node) {
		for(int i = 0; i < node.getChildNodes().getLength(); i++) {
			NodeList childNodes = node.getChildNodes();
			Node child = childNodes.item(i);
			if(child.getNodeType() == Node.TEXT_NODE) {
				return child.getNodeValue();
			}
		}
		return null;
	}
	
	protected Object getValueFromText(Entity entity, String attrName, Type attrType, String text) {
		Object object = null;
		if(attrType == String.class) {
			object = text;
		}
		if(attrType == Integer.class) {
			 object = Integer.parseInt(text);
		}
		else if(attrType == Long.class) {
			object = Long.parseLong(text);
		}
		else if(attrType == Float.class) {
			object = Float.parseFloat(text);
		}
		else if(attrType == Double.class) {
			object = Double.parseDouble(text);
		}
		else if(attrType == Date.class) {
			object = Utils.ParseDate(text);
			
		}
		else if(attrType == Boolean.class) {
			object = Boolean.parseBoolean(text);
		}
		else if(attrType == Short.class) {
			object = Short.parseShort(text);
		}
		else if(attrType == Character.class) {
			if(text.length() == 1) {
				object = text.charAt(0);
			}
		}
		else if(attrType == Byte.class) {
			object = Byte.parseByte(text);
		}
		else if(attrType == Type.class || attrType == Class.class) {
			Type classIns = null;
			try {
				classIns = Class.forName(text);
			} 
			catch (ClassNotFoundException e) {}
			if(classIns == null) {
				String className = "java.lang." + text;
				try {
					classIns = Class.forName(className);
				} 
				catch (ClassNotFoundException e) {}
			}
			if(classIns == null) {
				String className = "java.util." + text;
				try {
					classIns = Class.forName(className);
				}
				catch(ClassNotFoundException e){} 
			}
			object = classIns;
		}
		return object;
	}
}

